package com.dong.drool.autoconfigure.core;


import org.drools.core.SessionConfiguration;
import org.drools.core.impl.KnowledgeBaseImpl;
import org.kie.api.KieBase;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.definition.KiePackage;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.StatelessKieSession;
import org.kie.api.runtime.rule.FactHandle;
import org.kie.internal.builder.KnowledgeBuilder;
import org.kie.internal.builder.KnowledgeBuilderFactory;
import org.kie.internal.io.ResourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

/**
 * KieDrool规则引擎执行器的抽象类
 * 负责初始化规则引擎，并实现主要操作的方法
 *
 * @author zhaodongchao
 * @date 2019/2/26 10:26
 */
public abstract class AbstractKieDroolExecutor implements KieDroolExecutor {
    private static Logger logger = LoggerFactory.getLogger(AbstractKieDroolExecutor.class);
    private static KieServices kieServices = KieServices.Factory.get();
    private static ReleaseId releaseId = kieServices.getRepository().getDefaultReleaseId();

    private List<RuleEntity> rules;
    private KieContainer kieContainer;

    /**
     * 是否时调试模式，默认为false。设置为true时会打印一些详细的日志信息
     */
    private boolean debugFlag = false;

    public AbstractKieDroolExecutor() {
        init();
    }

    public AbstractKieDroolExecutor(boolean debugFlag) {
        init(null,debugFlag);
    }

    public AbstractKieDroolExecutor(List<RuleEntity> rules, boolean debug) {
        init(rules, debug);
    }

    public AbstractKieDroolExecutor(List<RuleEntity> rules) {
        init(rules, false);
    }

    private void init(){
        init(null,true);
    }
    private void init(List<RuleEntity> rules, boolean debug) {
        this.debugFlag = debug;
        Instant start = Instant.now();
        if (null == rules || rules.size() == 0) {
            rules = new ArrayList<>();
            if (debugFlag) {
                logger.warn("No rule was added when initializing the rules engine !");
            }
        }
        try {
            this.rules = rules;
            //初始化规则文件
            this.initKieServices(rules);
            kieContainer = kieServices.newKieContainer(releaseId);

            if (debug) {
                logger.info("The drool engine is initialized and takes time :" + Duration.between(start, Instant.now()).toMillis() + "ms");
            }

        } catch (Exception e) {
            if (debug) {
                logger.error("Drool engine init failed :" + e.getMessage());
            }
            throw e ;
        }

    }

    /**
     * 初始化KieService
     *
     * @param rules 规则实体集合
     */
    private void initKieServices(List<RuleEntity> rules) {
        KieFileSystem kieFileSystem = kieServices.newKieFileSystem();
        for (RuleEntity rule : rules) {
            kieFileSystem.write("src/main/resources/" + rule.getRuleName() + ".drl", rule.getContent());

            if (debugFlag) {
                logger.info("Add rule: " + rule.getRuleName() + " to KieFileSystem");
            }
        }

        KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem).buildAll();
        if (kieBuilder.getResults().hasMessages(Message.Level.ERROR)) {
            List<Message> errors = kieBuilder.getResults().getMessages(Message.Level.ERROR);
            StringBuilder sb = new StringBuilder("Errors:");
            errors.forEach(error -> sb.append("\n ").append(error.toString()));

            if (debugFlag) {
                logger.error(sb.toString());
            }
            throw new RuntimeException("编译规则时发生错误！");
        }
    }

    /**
     * 获取当前规则引擎中的规则信息
     *
     * @return List规则实体信息
     */
    @Override
    public List<RuleEntity> getRules() {
        return this.rules;
    }

    /**
     * 根据传入的规则信息，重新初始化规则引擎。耗时较长，谨慎操作!
     *
     * @param rules 规则实体信息集合
     */
    @Override
    public final void reInitByRules(List<RuleEntity> rules) {

        if (debugFlag) {
            logger.info(" ReInit drool now ...... ");
        }
        getKieContainer().dispose();

        this.init(rules, debugFlag);

        if (debugFlag) {
            logger.info(" ReInit drool successful !");
        }
    }

    @Override
    public int execute(Fact fact) {
        if (null == fact) {
            logger.info("传入fact为空！");
            return 0 ;
        }

        Instant start = Instant.now();
        int count = 0;
        KieSession kieSession = null;
        FactHandle factHandle = null;
        try {
            //使用默认配置初始化session
            kieSession = getKieContainer().newKieSession(SessionConfiguration.newInstance());
            factHandle = kieSession.insert(fact);
            count = kieSession.fireAllRules();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != kieSession) {
                kieSession.delete(factHandle);
                kieSession.dispose();
            }
        }
        if (debugFlag) {
            logger.info("执行" + count + "条规则耗时：" + Duration.between(start, Instant.now()).toMillis() + "ms");
        }
        return count;
    }

    @Override
    public void executeStateLess(Fact fact) {
        if (null == fact){
            return;
        }
        Instant start = Instant.now();
        StatelessKieSession session = getKieContainer().newStatelessKieSession();
        session.execute(fact);
        if (debugFlag){
            logger.info("执行完毕，耗时：" + Duration.between(start, Instant.now()).toMillis() + "ms");
        }

    }

    @Override
    public void addRule(RuleEntity rule) {
        KnowledgeBaseImpl kieBase = (KnowledgeBaseImpl) kieContainer.getKieBase();
        //重新添加规则
        KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
        //装入规则，可以装入多个
        kb.add(ResourceFactory.newByteArrayResource(rule.getContent().getBytes()), ResourceType.DRL);
        kieBase.addPackages(kb.getKnowledgePackages());
        this.rules.add(rule);

        if (debugFlag) {
            logger.info("Successful add a rule " + rule.getRuleName() + " to current KieBase!");
        }
    }


    @Override
    public void addRules(List<RuleEntity> rules) {
        KnowledgeBaseImpl kieBase = (KnowledgeBaseImpl) kieContainer.getKieBase();
        KnowledgeBuilder kb = KnowledgeBuilderFactory.newKnowledgeBuilder();
        rules.forEach(rule -> kb.add(ResourceFactory.newByteArrayResource(rule.getContent().getBytes()), ResourceType.DRL));
        kieBase.addPackages(kb.getKnowledgePackages());

        this.rules.addAll(rules);
        if (debugFlag) {
            logger.info("Successful add " + rules.size() + " new rules to current KieBase!");
        }
    }

    @Override
    public void removeRule(String packageName, String ruleName) {
        KieBase kieBase = kieContainer.getKieBase();
        //判断是否存在要删除的规则
        if (null == kieBase.getRule(packageName, ruleName)) {
            throw new IllegalArgumentException("A given package: " + packageName + " rule: " + ruleName + " does not exist !");
        }

        kieContainer.getKieBase().removeRule(packageName, ruleName);
        if (debugFlag) {
            logger.info("Successful delete a rule named " + ruleName + " in the package " + packageName);
        }
        RuleEntity rm = null;
        for (RuleEntity ruleEntity : rules) {
            if (ruleEntity.getRuleName().equals(ruleName)) {
                rm = ruleEntity;
                break;
            }
        }
        if (rm != null) {
            this.rules.remove(rm);
        }
    }

    @Override
    public void removeAllRules() {
        KieBase kieBase = kieContainer.getKieBase();
        for (KiePackage kiePackage : kieBase.getKiePackages()) {
            kieBase.removeKiePackage(kiePackage.getName());
        }
        this.rules = new ArrayList<>();

        if (debugFlag) {
            logger.info("Successful remove all rule in current container !");
        }
    }

    @Override
    public KieContainer getKieContainer() {
        return this.kieContainer;
    }

}
